use super::util::{
    escape_rust_keyword, format_field_ident, is_compound_field, trim_starting_raw_identifier,
};
use heck::ToUpperCamelCase;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::{Data, Expr, Fields, Lit, spanned::Spanned};

/// First is `struct TypedColumn`, second is the `const COLUMN`
pub fn expand_typed_column(data: &Data) -> syn::Result<(TokenStream, TokenStream)> {
    let mut column_fields = Vec::new();
    let mut column_types = Vec::new();
    let mut column_values = Vec::new();

    if let Data::Struct(item_struct) = &data {
        if let Fields::Named(fields) = &item_struct.fields {
            for field in &fields.named {
                if let Some(ident) = &field.ident {
                    let field_name = trim_starting_raw_identifier(ident);
                    let mut field_name =
                        Ident::new(&field_name.to_upper_camel_case(), ident.span());

                    let field_type = &field.ty;
                    let field_type = quote! { #field_type }
                        .to_string() // e.g.: "Option < String >"
                        .replace(' ', ""); // Remove spaces

                    let mut ignore = false;
                    let mut column_type = None;

                    for attr in field.attrs.iter() {
                        if attr.path().is_ident("sea_orm") {
                            attr.parse_nested_meta(|meta| {
                                if meta.path.is_ident("column_type") {
                                    let lit = meta.value()?.parse()?;
                                    if let Lit::Str(litstr) = lit {
                                        column_type = Some(litstr.value());
                                    } else {
                                        return Err(
                                            meta.error(format!("Invalid column_type {lit:?}"))
                                        );
                                    }
                                } else if meta.path.is_ident("enum_name") {
                                    let lit = meta.value()?.parse()?;
                                    if let Lit::Str(litstr) = lit {
                                        let ty: Ident = syn::parse_str(&litstr.value())?;
                                        field_name = ty;
                                    } else {
                                        return Err(
                                            meta.error(format!("Invalid enum_name {lit:?}"))
                                        );
                                    }
                                } else if meta.path.is_ident("ignore") {
                                    ignore = true;
                                } else {
                                    // Reads the value expression to advance the parse stream.
                                    let _: Option<Expr> = meta.value().and_then(|v| v.parse()).ok();
                                }

                                Ok(())
                            })?;
                        }
                    }

                    if ignore {
                        continue;
                    }

                    if is_compound_field(&field_type) {
                        continue;
                    }

                    field_name = Ident::new(&escape_rust_keyword(field_name), ident.span());

                    let field_type = &field.ty;
                    let field_type = quote! { #field_type }
                        .to_string() // e.g.: "Option < String >"
                        .replace(' ', ""); // Remove spaces

                    column_fields.push(format_field_ident(field));
                    let wrapper = super::value_type_match::column_type_wrapper(
                        &column_type,
                        &field_type,
                        field.span(),
                    );
                    column_types.push(if let Some(wrapper) = &wrapper {
                        quote!(sea_orm::#wrapper<Entity>)
                    } else {
                        quote!(Column)
                    });
                    column_values.push(if let Some(wrapper) = &wrapper {
                        quote!(sea_orm::#wrapper(Column::#field_name))
                    } else {
                        quote!(Column::#field_name)
                    });
                }
            }
        }
    }

    Ok((
        quote! {
            #[doc = " Generated by sea-orm-macros"]
            pub struct TypedColumn {
                #(
                    #[doc = " Generated by sea-orm-macros"]
                    pub #column_fields: #column_types
                ),*
            }
        },
        quote! {
            #[doc = " Generated by sea-orm-macros"]
            pub const COLUMN: TypedColumn = TypedColumn {
                #(#column_fields: #column_values),*
            };
        },
    ))
}
